﻿// -----------------------------------------------------------------------
// <copyright file="Face_Recognition.cs" company="FCIS">
// TODO: Update copyright text.
// </copyright>
// -----------------------------------------------------------------------

namespace FaceRecognition.Models
{
    using System;
    using System.Collections.Generic;
    using System.Drawing;
    using System.Drawing.Drawing2D;
    using System.Drawing.Imaging;
    using System.Linq;
    using System.Text;
    using System.Timers;
    using Emgu.CV;
    using Emgu.CV.CvEnum;
    using Emgu.CV.Structure;

    /// <summary>
    /// TODO: Update summary.
    /// </summary>
    public class Face_Recognition : IFace_Recognition
    {
        /// <summary>
        /// Variable For Quantization To Use It In Orientation Function.
        /// </summary>
        private int tcapital;
        
        /// <summary>
        /// Number Of Pins.
        /// </summary>
        private int s;
        
        /// <summary>
        /// Number Of Regions.
        /// </summary>
        private int mcapital;
        
        /// <summary>
        /// Number Of Particular Region.
        /// </summary>
        private int m;

        /// <summary>
        /// Initializes a new instances of Quantization, Number of Pins and Regions By Default Values.
        /// </summary>
        public Face_Recognition()
        {
            this.tcapital = 8;
            this.s = 20;
            this.mcapital = 6;
            this.regions = new Region[this.mcapital];

            for (int i = 0; i < this.mcapital; i++)
            {
                this.regions[i].Lower = ((i / this.mcapital) - 0.5) * Math.PI;
                this.regions[i].Upper = (((i + 1) / this.mcapital) - 0.5) * Math.PI;
            }
        }

        /// <summary>
        /// Initializes a new instances of Quantization, Number of Pins and Regions By The User.
        /// </summary>
        /// <param name="tcap">Quantization Value for Orientation.</param>
        /// <param name="scap">Number Of Pins.</param>
        /// <param name="mcap">Number Of Regions.</param>
        public Face_Recognition(int tcap, int scap, int mcap)
        {
            this.tcapital = tcap;
            this.s = scap;
            this.mcapital = mcap;
            this.regions = new Region[this.mcapital];

            for (int i = 0; i < this.mcapital; i++)
            {
                this.regions[i].Lower = ((i / this.mcapital) - 0.5) * Math.PI;
                this.regions[i].Upper = (((i + 1) / this.mcapital) - 0.5) * Math.PI;
            }
        }

        /// <summary>
        /// Takes The Excitation Value and Find Out it Lies in Which Region.
        /// </summary>
        /// <param name="exciValue">The Excitation Value.</param>
        /// <returns>The Region.</returns>
        public int Check_Which_region(double exciValue)
        {
            for (int i = 0; i < this.mcapital; i++)
            {
                if (exciValue >= this.regions[i].Lower && exciValue < this.regions[i].Upper)
                {
                    return i;
                }
            }

            return -1;
        }

        /// <summary>
        /// Calculates the Excitation to an exact pixel.
        /// </summary>
        /// <param name="img">The Original Image.</param>
        /// <param name="windowsize">The Mask that walks on image.</param>
        /// <param name="i">The parameter for the Rows.</param>
        /// <param name="j">The parameter for the Coloumns.</param>
        /// <returns>Returns the excitation value.</returns>
        public double Excitation(byte[,] img, int windowsize, int i, int j)
        {
            int xc;
            int xn;
            double sum = 0;
            double sub = 0;
            double div = 0;
            double result = 0;

            xc = img[i, j];
            sub = sum = 0;
            for (int a = i - (windowsize / 2); a <= i + (windowsize / 2); a++)
            {
                for (int b = j - (windowsize / 2); b <= j + (windowsize / 2); b++)
                {
                    xn = img[a, b];
                    sub = xn - xc;
                    sum += sub;
                }
            }

            div = sum / xc;
            if (xc == 0)
            {
                div = 1;
            }

            result = Math.Atan(div);
            return result;
        }

        /// <summary>
        /// Calculates the Orientation to an exact pixel.
        /// </summary>
        /// <param name="img">The Original Image.</param>
        /// <param name="windowsize">The Mask that walks on image.</param>
        /// <param name="i">The parameter for the Rows.</param>
        /// <param name="j">The parameter for the Coloumns.</param>
        /// <param name="tcapital">The Quantization Value.</param>
        /// <returns>Returns the orientation value.</returns>
        public double Orientation(byte[,] img, int windowsize, int i, int j, int tcapital)
        {
            int x1, x5, x7, x3;
            double v10, v11;
            double theta;
            double theta_Bar;
            double choice = 0;
            double t;
            double fai;
            double result;

            x1 = img[i - (windowsize / 2), j];
            x3 = img[i, j + (windowsize / 2)];
            x5 = img[i + (windowsize / 2), j];
            x7 = img[i, j - (windowsize / 2)];

            v10 = x5 - x1;
            v11 = x7 - x3;

            theta = (float)Math.Atan(v11 / v10);

            if (v11 > 0 && v10 > 0)
            {
                choice = theta;
            }
            else if (v11 > 0 && v10 < 0)
            {
                choice = theta + (float)Math.PI;
            }
            else if (v11 < 0 && v10 < 0)
            {
                choice = theta - (float)Math.PI;
            }
            else if (v11 < 0 && v10 > 0)
            {
                choice = theta;
            }

            theta_Bar = choice + (float)Math.PI;

            t = Math.Floor((theta_Bar / ((2 * (float)Math.PI) / tcapital)) + (1 / 2)) % tcapital;
            fai = ((2 * t) / tcapital) * (float)Math.PI;
            result = fai;
            return result;
        }

        /// <summary>
        /// takes an image and return it's buffer after feature extraction.
        /// </summary>
        /// <param name="img">The image.</param>
        /// <returns>The Array of Features.</returns>
        public double[] Extract_Features(byte[,] img)
        {
            int height = ImageOperations.GetHeight(img);
            int width = ImageOperations.GetWidth(img);
            int exci_count = 0;
            int orien_count = 0;
            int exci_index = 0;
            int orien_index = 0;
            int heightInWidth = height * width;
            double sJ;
            double[] excitation_Buffer = new double[heightInWidth];
            double[] orientation_Buffer = new double[heightInWidth];
            int[,] both_Buffers = new int[(int)(this.mcapital * this.s ), (int)this.tcapital];
            int winsize = 3;

            for (int a = 0; a < this.mcapital * this.s ; a++)
            {
                for (int b = 0; b < this.tcapital; b++)
                {
                    both_Buffers[a, b] = 0;
                }
            }

            for (int i = winsize / 2; i < height - (winsize / 2); i++)
            {
                for (int j = winsize / 2; j < width - (winsize / 2); j++)
                {
                    excitation_Buffer[exci_count] = this.Excitation(img, winsize, i, j);
                    orientation_Buffer[orien_count] = this.Orientation(img, winsize, i, j, this.tcapital);
                    this.m = this.Check_Which_region(excitation_Buffer[exci_count]);
                    sJ = Math.Floor(((excitation_Buffer[exci_count] - this.regions[this.m].Lower) / ((this.regions[this.m].Upper - this.regions[this.m].Lower) / this.s)) );
                    exci_index = (this.m * this.s) + (int)sJ;
                    orien_index = (int)((orientation_Buffer[orien_count] / (2 * Math.PI)) * this.tcapital);
                    both_Buffers[exci_index, orien_index]++;
                    exci_count++;
                    orien_count++;
                }
            }
            
            int index_of_Array = 0;
            double[] d_arr = new double[(int)(this.mcapital * this.s * this.tcapital )];

            for (int a = 0; a < this.mcapital ; a++)
            {
                for (int b = 0; b < this.tcapital; b++)
                {
                   for(int i=0 ; i<s ; i++)
                   {
                        d_arr[index_of_Array] = both_Buffers[i+(a*s), b];
                        index_of_Array++;
                    }
                }
            }
            
            return d_arr;
        }
        
        /// <summary>
        /// Make a Compare Between Two Arrays and Return The Root value.
        /// </summary>
        /// <param name="x">First Array.</param>
        /// <param name="y">Second Array.</param>
        /// <returns>The Root Value.</returns>
        public double Matching(double[] x, double[] y)
        {
            double sum = 0;
            double root = 0;

            for (int i = 0; i < x.Length; i++)
            {
                sum += ((x[i] - y[i])/(x[i]+1)) * ((x[i] - y[i])/(x[i]+1));
                //sum += ((x[i] - y[i]) ) * ((x[i] - y[i]) );
            }

            root = Math.Sqrt(sum);
            return root;
        }

        /// <summary>
        /// Structure Of Regions To Carry Lower and Upper Values.
        /// </summary>
        private struct Region
        {
            /// <summary>
            /// The Upper Boundary.
            /// </summary>
            public double Upper;

            /// <summary>
            /// The Lower Boundary.
            /// </summary>
            public double Lower;
        }

        /// <summary>
        /// Array Of Regions.
        /// </summary>
        private Region[] regions;
    }
}